/**
 * 2017年5月21日
 */
package cn.edu.bjtu.model.core;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.io.FileUtils;
import org.deeplearning4j.iterator.LabeledSentenceProvider;
import org.deeplearning4j.iterator.provider.CollectionLabeledSentenceProvider;
import org.deeplearning4j.models.embeddings.loader.WordVectorSerializer;
import org.deeplearning4j.models.word2vec.Word2Vec;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.api.DataSet;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.textplantform.common.api.AfterInitializationExecute;
import org.textplantform.common.core.ClassificationPair;
import org.textplantform.common.provider.ClassificationService;
import org.textplantform.common.provider.NetworkModelService;

import cn.edu.bjtu.util.DatasetUtils;

/**
 * 由且必须由Spring容器初始化
 * 因为依赖PRC服务获取到类标等之后才能得到TextCategorizationManager类实例.
 * 模型中的rwLock什么时候有用呢.
 * 就是在ModelNetworkService中获取lastModeified时间大于当前模型时间时就会重新initNet()
 * 需要一个后台线程来完成这个工作,还没完成.
 * 一般来说以_开关的方法是被private方法调用的
 * 这个类只专注于分类,消息队列的代码分离到另外一个类里面.
 * 
 * @author Alex
 * @param <ClassificationPair>
 *
 */
public class TextCategorizationCNNModel extends SpringContextSupport implements ClassificationService,BeanFactoryAware,AfterInitializationExecute,DisposableBean{
	//这里用读写锁的原因是,当common RPC返回模型修改时间发生变化时还需要重新加.加载的时候使用写锁.
	protected ReadWriteLock rwLock = new ReentrantReadWriteLock();
	protected Word2Vec w2v = null;
	protected Deep4jNetworkWrapper net;
	@Autowired private NetworkModelService nms;
	protected volatile int maxSentenceLength = 0;
	
	
	public TextCategorizationCNNModel(){
	
	}
	private void _setConfigs(){
		maxSentenceLength = nms.getSentenceLength();
	}

	private void initW2vAndNet(){
		try{
			rwLock.writeLock().lock();
			//readWord2VecModel只能读入文件,不能读流,所以打包之后就无法读取了
			//使用流从jar包读出写入临时文件,然后加载临时文件
			if(w2v == null){
				_initw2v();
			}
			_initNet();
		}catch(Exception e){
			throw new RuntimeException(e.getMessage(),e);
		}finally{
			rwLock.writeLock().unlock();
		}
	}
	//模型更新时候需要调用RPC获取那个cnn.bin文件
	public void restoreFromFile(){
		initW2vAndNet();
	}
	private void _initNet() throws Exception{
		TextCategorizationManager.restore(this.bf);
		net = __newNetworkByTypeNotInit();
		net.load();
		logger.info("Success load neuron network,type is {} ,at {}",net.getActualType(),new Date());
	}
	private void _initw2v() throws Exception{
		if(w2v == null){
			File temp = File.createTempFile("word2vec", "wordembedding");
			temp.deleteOnExit();
			InputStream in = this.getClass().getClassLoader().getResourceAsStream("model/fdModel_100.txt");
			FileUtils.copyInputStreamToFile(in, temp);
			w2v = WordVectorSerializer.readWord2VecModel(temp);
			if(temp.delete()){
				logger.info("Temp file {} delete success!",temp.getAbsoluteFile());
			}else{
				logger.warn("Temp file {} delete FAILED!,Disk free space",temp.getAbsoluteFile());
			}
			if(w2v == null){
				throw new IllegalArgumentException("word2vec read failed! check path in your cnnconfig.properties 'WORD2VEC_MODEL_PATH'");
			}
		}
	}
	/**
	 * 同步方法
	 * 这个返回的是每个类别的概率
	 * @param doc
	 * @return
	 */
	public INDArray[] predictDocument(String doc){
		List<String> docs = new ArrayList<String>(1);
		docs.add(doc);
		List<String> labels = new ArrayList<>(1);
		labels.add("unknow");
		LabeledSentenceProvider lsp = new CollectionLabeledSentenceProvider(docs,labels);
		INDArray[] res = null;
		try{
			//一般只会有一个文档,所以直接output当多于batchSize时,会出现问题,不过一般没问题,当处理大批量文档的时候需要重写一个这个返回的结果
			DataSetIterator dsi = DatasetUtils.getCNNDataSet(lsp, 1, maxSentenceLength, w2v);
			
			rwLock.readLock().lock();
			while(dsi.hasNext()){
				DataSet ds = dsi.next();
				try {
					res = net.output(ds.getFeatures());
				} catch (Exception e) {
					logException(e);
					res = null;
				}
			}
			return res;
		}finally{
			rwLock.readLock().unlock();
		}
	}
	
	
	public ClassificationPair[] predictDocumentLabelString(String doc){
		INDArray[] res = predictDocument(doc);
		INDArray labelVal = res[0];
		return TextCategorizationManager.get().getDesc(labelVal);
	}
	@Override
	public ClassificationPair[][] classifyDocument(String[] docs) throws Exception {
		//确定初始长度,以减少gc次数
		List<String> docList = new ArrayList<String>(docs.length);
		List<String> labelList = new ArrayList<>(docs.length);
		for(int i=0;i<docs.length;i++){
			docList.add(docs[i]);
			labelList.add("unknow");
		}
		//第三个参数一定要有,防止洗牌
		LabeledSentenceProvider lsp = new CollectionLabeledSentenceProvider(docList,labelList,null);
		List<INDArray[]> resList = new ArrayList<INDArray[]>(5);
		try{
			DataSetIterator dsi = DatasetUtils.getCNNDataSet(lsp, 32, maxSentenceLength, w2v);
			rwLock.readLock().lock();
			while(dsi.hasNext()){
				DataSet ds = dsi.next();
				resList.add(net.output(ds.getFeatures()));
			}
		}finally{
			rwLock.readLock().unlock();
		}
		//减少同步块.
		ClassificationPair[][] result = new ClassificationPair[docs.length][];
		int k = 0;
		for(int i=0;i<resList.size();i++){
			INDArray ele = resList.get(i)[0];
			for(int j = 0;j<ele.rows();j++){
				result[k++] = TextCategorizationManager.get().getDesc(ele.getRow(j));
			}
		}
		return result;
	}
	
	private Deep4jNetworkWrapper __newNetworkByTypeNotInit(){
		if(net!=null){
			return net;
		}
		return (net = new Deep4jNetworkWrapper(nms,-1));
	}
	/**
	 * FOR rpc
	 */
	@Override
	public ClassificationPair[] classifyDocument(String doc) throws Exception {
		return predictDocumentLabelString(doc);
	}
	/**
	 * 让Spring去调用.
	 */
	private void initConfigsAndInitModel(){
			_setConfigs();
			initW2vAndNet();
	}
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		super.setBeanFactory(beanFactory);
	}
	//这个方法由Spring调用
	@Override
	public void executeAfterBeanInitialization() {
		initConfigsAndInitModel();
	}

	@Override
	public void destroy() throws Exception {
	}


	
}
